package com.nx.platform.es.bean.modle.query;

import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Range;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.nx.platform.es.biz.wrapper.parser.StatementOperator;
import com.nx.platform.es.common.utils.MoreSplitters;
import com.nx.platform.es.bean.dto.RequestContext;
import com.nx.platform.es.common.utils.CommonUtils;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class ParamHandler implements QueryFieldHandler {

    private static final Pattern FORMAT_SINGLE = Pattern.compile("^\\d++$");
    private static final Pattern FORMAT_SOME = Pattern.compile("^\\d++(?:\\|\\d++)++$");
    private static final Pattern FORMAT_RANGE = Pattern.compile("^(\\d++|\\*)_(\\d++|\\*)$");

    @Override
    public void handle(ImmutableMap<String, ?> fieldConfig, RequestContext context, String fieldFace,
                       StatementOperator operator, String fieldValue, ListMultimap<Boolean, QueryBuilder> queryBuilders) {
        // 空值
        if (Strings.isNullOrEmpty(fieldValue)) {
            return;
        }
        String fieldName = MapUtils.getString(fieldConfig, "fieldName");
        Preconditions.checkState(!Strings.isNullOrEmpty(fieldName));
        String[] split = MoreSplitters.COMMA.split(fieldValue);
        Preconditions.checkState(split.length == 2);
        final Integer paramKey = Ints.tryParse(split[0]);
        final String paramValue = split[1];
        final String clazz = MapUtils.getString(fieldConfig, "class");
        Preconditions.checkState(paramKey != null, "paramKey not integer, paramKey=" + split[0]);
        // 多个值
        if (FORMAT_SOME.matcher(paramValue).matches()) {
            List<Object> set = MoreSplitters.VERTICAL.splitAsStream(paramValue)
                    .map(String::trim).map(e -> parse(clazz, paramKey, e)).filter(Objects::nonNull)
                    .distinct().sorted().collect(Collectors.toList());
            if (!set.isEmpty()) {
                context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(Joiner.on('|').join(set));
                queryBuilders.put(operator.isSign(), QueryBuilders.termsQuery(fieldName, set));
            }
            return;
        }
        // 单个值
        if (FORMAT_SINGLE.matcher(paramValue).matches()) {
            Object value = parse(clazz, paramKey, paramValue);
            Preconditions.checkState(value != null, "paramValue illegal, paramValue=" + paramValue);
            context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(value);
            queryBuilders.put(operator.isSign(), QueryBuilders.termQuery(fieldName, value));
            return;
        }
        // 区间
        Matcher rangeMatcher = FORMAT_RANGE.matcher(paramValue);
        if (rangeMatcher.find()) {
            String left = rangeMatcher.group(1);
            String right = rangeMatcher.group(2);
            Integer ln = Ints.tryParse(left);
            Integer rn = Ints.tryParse(right);
            if (left.equals(right) && !"*".equals(left)) {
                Preconditions.checkState(ln != null, "paramValue not integer, paramValue=" + left);
                long value = CommonUtils.merge(paramKey, ln);
                context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(value);
                queryBuilders.put(operator.isSign(), QueryBuilders.termQuery(fieldName, value));
                return;
            } else if (!"*".equals(left) && !"*".equals(right)) {
                Preconditions.checkState(ln != null, "paramValue not integer, paramValue=" + left);
                Preconditions.checkState(rn != null, "paramValue not integer, paramValue=" + right);
                try {
                    Range<Long> range = Range.openClosed(CommonUtils.merge(paramKey, ln), CommonUtils.merge(paramKey, rn));
                    context.getTrace().append("&").append(fieldFace).append(operator.getSymbol())
                            .append(range.lowerEndpoint()).append("_").append(range.upperEndpoint());
                    queryBuilders.put(operator.isSign(),
                            QueryBuilders.rangeQuery(fieldName).from(range.lowerEndpoint())
                                    .to(range.upperEndpoint()).includeLower(true).includeUpper(true));
                    return;
                } catch (Exception nfe) {
                    throw new IllegalArgumentException("format incorrect: field=" + fieldName + ", value=" + paramValue, nfe);
                }
            } else if (!"*".equals(left)) {
                Preconditions.checkState(ln != null, "paramValue not integer, paramValue=" + left);
                long value = CommonUtils.merge(paramKey, ln);
                context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append("*_").append(value);
                queryBuilders.put(operator.isSign(), QueryBuilders.rangeQuery(fieldName).gte(value));
                return;
            } else if (!"*".equals(right)) {
                Preconditions.checkState(rn != null, "paramValue not integer, paramValue=" + right);
                long value = CommonUtils.merge(paramKey, rn);
                context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(value).append("_*");
                queryBuilders.put(operator.isSign(), QueryBuilders.rangeQuery(fieldName).lte(value));
                return;
            }
        }
        throw new IllegalArgumentException("format incorrect: field=" + fieldName + ", value=" + paramValue);
    }

    protected Object parse(String clazz, Integer key, String str) {
        return Enums.getIfPresent(Parsers.class, clazz).or(Parsers.Integer).parse(key, str);
    }

    enum Parsers {
        Integer {
            @Override
            Long parse(Integer key, String str) {
                Integer val = Ints.tryParse(str);
                return val == null ? null : CommonUtils.merge(key, val);
            }
        },
        Long {
            @Override
            String parse(Integer key, String str) {
                Long val = Longs.tryParse(str);
                return val == null ? null : CommonUtils.merge(key, val);
            }
        };

        abstract Object parse(Integer key, String str);

    }

}
